package jaux.tank.generator.component;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileNameExtensionFilter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

import jaux.tank.generator.MainApp;
import jaux.tank.generator.domain.TankApplication;
import jaux.tank.generator.domain.TankConstant;
import jaux.tank.generator.domain.TankEntity;
import jaux.tank.generator.domain.TankEntityRelationship;
import jaux.tank.generator.domain.TankEnum;
import jaux.tank.generator.domain.TankRoot;
import jaux.tank.generator.util.ConfigUtil;
import jaux.tank.generator.util.MessageUtil;
import jaux.tank.generator.util.TextUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 *
 * @author hxd
 */
@Slf4j
public class TankGeneratorFrame extends JFrame {
	
	private static final long serialVersionUID = 1L;
	private static final String APP_TITLE_MSG_KEY = "app.title";
	private static final String APP_NAME_MSG_KEY = "app.name";
	private TankGeneratorMenuBar tankGeneratorMenuBar;
	private TankGeneratorToolBar toolBar;
	private TankTree tree;
	private TankTabbedPane tabbedPane;
	private JLabel statusLabel;
	@Getter
	private TankRoot tankRoot;
	@Getter
	private boolean tankRootChanged = false;
	private MainApp generatorApp;
	
	@Getter
	private File tankFile;

	public TankGeneratorFrame(MainApp app) {
		this.generatorApp = app;
		setBounds(100, 100, 900, 600);
		setMinimumSize(new Dimension(900, 600));
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		addWindowListener(new java.awt.event.WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				if(checkTankSaved()) {
					System.exit(0);
				}
			}
		});
		setTitle(getString(APP_NAME_MSG_KEY));

		tankGeneratorMenuBar = new TankGeneratorMenuBar(this);
		setJMenuBar(tankGeneratorMenuBar);

		toolBar = new TankGeneratorToolBar(this);
		getContentPane().add(toolBar, BorderLayout.NORTH);
		
		tree = new TankTree(this);
		JScrollPane treeScrollPane = new JScrollPane(tree);
		treeScrollPane.setMinimumSize(new Dimension(200, 0));
		tabbedPane = new TankTabbedPane(this);
		JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScrollPane, tabbedPane);
		getContentPane().add(splitPane, BorderLayout.CENTER);

		statusLabel = new JLabel("Status");
		getContentPane().add(statusLabel, BorderLayout.SOUTH);
	}

	public boolean checkTankSaved() {
		if(isTankSaveNeeded()) {
			// ask save
			return MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.root.ignore.unsaved") );
		}
		return true;
	}
	
	public void deleteApplication(TankApplication app) {
		int tankAppsCount = tankRoot.getApplications().size();
		if(tankAppsCount > 1) {
			// ask delete app
			if(MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.app.delete"))) {
				tankRoot.getApplications().remove(app);
				tankRootChanged = true;
				refreshTankRoot();
			}
		}else {
			// can not delete last app
			MessageUtil.showError(TankGeneratorFrame.this,getString("message.error.app.delete.last"));
		}
	}
	
	public void deleteEntity(TankEntity entity) {
		// ask delete entity
		if(MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.entity.delete"))) {
			tankRoot.getEntities().remove(entity);
			tankRootChanged = true;
			refreshTankRoot();
		}
	}

	public void deleteRelationship(TankEntityRelationship rel) {
		// ask delete relationship
		if(MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.relationship.delete"))) {
			tankRoot.getRelationships().remove(rel);
			tankRootChanged = true;
			refreshTankRoot();
		}
	}

	public void openApplicationTab(TankApplication app, boolean newApp) {
		tabbedPane.openApplicationTab(app, newApp); 
	}

	public void addApplication() {
		// ask app name
		String newAppName = MessageUtil.getInput(TankGeneratorFrame.this,getString("message.input.app.name"));
		if(newAppName == null|| newAppName.trim().isEmpty()) {
			return;
		}
		// add app
		 
		Optional<TankApplication> dupApp = tankRoot.getApplications().stream()
				.filter(app->app.getName().equals(newAppName)).findAny();
		if(dupApp.isPresent()) {
			// app already exists
			MessageUtil.showError(TankGeneratorFrame.this,getString("message.error.app.already.exists"));
			return;
		}
		TankApplication newApp = new TankApplication(newAppName);
		tankRoot.getApplications().add(newApp);
		tankRootChanged = true;
		refreshTankRoot();
		openApplicationTab(newApp, true);
	}

	public void addEntity() {
		// ask entity name
		String newEntityName = MessageUtil.getInput(TankGeneratorFrame.this, getString("message.input.entity.name"));
		if(newEntityName == null) {
			return;
		}
		// check duplicate entity exists
		Optional<TankEntity> dupEntity = tankRoot.getEntities().stream()
				.filter(entity->entity.getName().equals(newEntityName)).findAny();
		if(dupEntity.isPresent()) {
			// entity already exists
			MessageUtil.showError(TankGeneratorFrame.this,getString("message.error.entity.already.exists"));
			return;
		}
		
		TankEntity newEntity = new TankEntity(newEntityName);
		tankRoot.getEntities().add(newEntity);
		tankRootChanged = true;
		refreshTankRoot();
		openEntityTab(newEntity, true);
	}

	public void addRelationship() {
		// ask relationship name
		String newRelationshipName = MessageUtil.getInput(TankGeneratorFrame.this, getString("message.input.relationship.name"));
		if(newRelationshipName == null) {
			return;
		}
		// check duplicate relationship exists
		if(tankRoot.getRelationships().stream()
				.anyMatch(relationship->relationship.getName().equals(newRelationshipName))) {
			// relationship already exists
			MessageUtil.showError(TankGeneratorFrame.this,getString("message.error.relationship.already.exists"));
			return;
		}
		
		TankEntityRelationship newRelationship = new TankEntityRelationship(newRelationshipName);
		tankRoot.getRelationships().add(newRelationship);
		tankRootChanged = true;
		refreshTankRoot();
		openRelationshipTab(newRelationship, true);
	}
	
	public void openEntityTab(TankEntity entity, boolean newEntity) {
		tabbedPane.openEntityTab(entity, newEntity);
	}
	
	public void openRelationshipTab(TankEntityRelationship relationship, boolean newRelationship) {
		tabbedPane.openRelationshipTab(relationship, newRelationship);
	}
	
	public void addTankEnum() {
		// ask relationship name
		String newEnumName = JOptionPane.showInputDialog(this, getString("message.input.enum.name"));
		if(newEnumName == null) {
			return;
		}
		// check duplicate relationship exists
		if(tankRoot.getEnums().stream()
				.anyMatch(tankEnum->tankEnum.getName().equals(newEnumName))) {
			// relationship already exists
			JOptionPane.showMessageDialog(this, getString("message.error.enum.already.exists") );
			return;
		}
		
		TankEnum newEnum = new TankEnum(newEnumName);
		tankRoot.getEnums().add(newEnum);
		tankRootChanged = true;
		refreshTankRoot();
		openEnumTab(newEnum, true);
	}
	public void deleteTankEnum(TankEnum tankEnum) {
		
		// ask delete enum
		if(MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.enum.delete"))) {
			tankRoot.getEnums().remove(tankEnum);
			tankRootChanged = true;
			refreshTankRoot();
		}
		
	}
	public void openEnumTab(TankEnum tankEnum, boolean newEnum) {
		tabbedPane.openEnumTab(tankEnum, newEnum);
	}
	public void focusTree() {
		tree.requestFocusInWindow();
	}
	
	void newTankRoot() {
		if(isTankSaveNeeded() && (MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.root.ignore.unsaved")))) {
				return;
			
		}
		tabbedPane.setAllEditorSaved(false);
		tankRootChanged = true;
		// ask tank root name
		String newProjectName = JOptionPane.showInputDialog(this, getString("message.input.root.name"));
		if(newProjectName == null) {
			return;
		}
		tankRoot = createTankRoot(newProjectName);
		tree.setTankRoot(tankRoot);
		tabbedPane.removeAll();
		setTitle(getString(APP_TITLE_MSG_KEY) + " - " + newProjectName);
	}

	/**
	 * 检查tankRoot是否已经保存
	 * @return 
	 */
	public boolean isTankSaveNeeded() {
		// 没有tankRoot实例存在或者tankRoot没有改变或者所有编辑器都没有改变
		return tankRoot != null && (tankRootChanged || !tabbedPane.isAllEditorSaved());
	}

	private void refreshTankRoot() {
		// store currently tree status  
		Map<String, Boolean> treeStates = tree.getAllNodeStates();
		tree.setTankRoot(tankRoot);
		tree.setAllNodeStates(treeStates);
		
		// refresh tree,dont reset tree model
		
		// refresh tabbed pane
		tabbedPane.refresh();
		// refresh status
//		statusLabel.setText(getString("status.updated"));
	}
	
	private TankRoot createTankRoot(String name) {
		TankRoot newRoot = new TankRoot();
		newRoot.setName(name);
		newRoot.setId(UUID.randomUUID().toString());
		String time = DateTimeFormatter.ofPattern(TankRoot.TIME_FORMAT).format(OffsetDateTime.now());
		newRoot.setCreatedTime(time);
		newRoot.setUpdatedTime(time);
		tankFile = null;
		return newRoot;
	}

	

	void openTank() {
		if(isTankSaveNeeded()) {
			// ask save
			int option = JOptionPane.showConfirmDialog(this, getString("message.confirm.root.ignore.unsaved"), "Confirm", JOptionPane.YES_NO_OPTION);
			if(option == JOptionPane.NO_OPTION) {
				return;
			}
		}
		
		JFileChooser fileChooser = new JFileChooser();
		FileNameExtensionFilter filter = new FileNameExtensionFilter("Tank Project File", "tank");
		fileChooser.setFileFilter(filter);
		int returnValue = fileChooser.showOpenDialog(this);
		if (returnValue == JFileChooser.APPROVE_OPTION) {
			tankFile = fileChooser.getSelectedFile();
			loadTankFile();
		}
	}

	private void loadTankFile() {
		if(tankFile == null) {
			return;
		}
		try {
			tankRoot = new ObjectMapper().readValue(tankFile, TankRoot.class);
			tree.setTankRoot(tankRoot);
			tabbedPane.removeAll();
			setTitle(getString(APP_TITLE_MSG_KEY) + " - " + tankRoot.getName());
			
			tabbedPane.setAllEditorSaved(true);
			tankRootChanged = false;
		} catch (IOException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
	}

	
	
	protected void saveTank() {
		// check tank exists
		if(tankRoot == null) {
			return;
		}
		updateTankRoot();
		saveTankFile(tankFile !=null);
	}
	
	protected void saveAsTank() {
		// check tank exists
		if(tankRoot == null) {
			return;
		}
		updateTankRoot();
		tankFile = null;
		saveTankFile(false);
	}

	protected void closeTank(){
		if(isTankSaveNeeded() && (MessageUtil.isYes(TankGeneratorFrame.this, getString("message.confirm.root.ignore.unsaved")))) {
				return;
			
		}
		exitTank();
	}
	
	void exit() {
		if(isTankSaveNeeded() && (MessageUtil.isYes(TankGeneratorFrame.this, getString("message.confirm.root.ignore.unsaved")))) {
			return;
			
		}
		exitTank();
		System.exit(0);
			
	}
	public void exitTank() {
		tabbedPane.setAllEditorSaved(true);
		tankRootChanged = false;
		tankFile = null;
		tankRoot = null;
		tree.setModel(null);
		tabbedPane.removeAll();
		setTitle(getString(APP_NAME_MSG_KEY));
	}
	private void updateTankRoot() {
		// update tank root by tabbed pane
		// 1. update application
		List<TankApplication> editedApps = tabbedPane.getAllApplications();
		tankRoot.setApplications(tankRoot
				.getApplications().stream().map(app ->{
					Optional<TankApplication> editedApp = editedApps.stream().filter(a -> app.getId().equals(a.getId())).findAny();
					if(editedApp.isPresent()) {
						TankApplication newApp = editedApp.get();
						log.debug("replace app: {} with {}",  app, newApp);
						return newApp;
					}
					return app;
				})
				.collect(Collectors.toList()));
		// 2. update entity
		List<TankEntity> editedEntities = tabbedPane.getAllEntities();
		tankRoot.setEntities(tankRoot
				.getEntities().stream().map(entity ->{
					Optional<TankEntity> editedEntity = editedEntities.stream().filter(e -> entity.getId().equals(e.getId())).findAny();
					if(editedEntity.isPresent()) {
						TankEntity newEntity = editedEntity.get();
						log.debug("replace entity: {} with {}", entity, newEntity);
						return newEntity;
					}
					return entity;
				})
				.collect(Collectors.toList()));
		// 3. update entity relationship
		List<TankEntityRelationship> editedRelationships = tabbedPane.getAllRelationships();
		tankRoot.setRelationships(tankRoot
				.getRelationships().stream().map(rel ->{
					Optional<TankEntityRelationship> editedRel = editedRelationships.stream().filter(r -> rel.getId().equals(r.getId())).findAny();
					if(editedRel.isPresent()) {
						TankEntityRelationship newRel = editedRel.get();
						log.debug("replace rel: {} with {}", rel, newRel);
						return newRel;
					}
					return rel;
				})
				.collect(Collectors.toList()));
		// 4. update enums
		List<TankEnum> editedEnums = tabbedPane.getAllEnums();
		tankRoot.setEnums(tankRoot
				.getEnums().stream().map(enumObj ->{
					Optional<TankEnum> editedEnum = editedEnums.stream().filter(e -> enumObj.getName().equals(e.getName())).findAny();
					if(editedEnum.isPresent()) {
						TankEnum newEnum = editedEnum.get();
						log.debug("replace enum: {} with {}", enumObj, newEnum);
						return newEnum;
					}
					return enumObj;
				})
				.collect(Collectors.toList()));
		// 5. update constants
		List<TankConstant> editedConstants = tabbedPane.getAllConstants();
		tankRoot.setConstants(tankRoot
				.getConstants().stream().map(constant ->{
					Optional<TankConstant> editedConstant = editedConstants.stream().filter(c -> constant.getName().equals(c.getName())).findAny();
					if(editedConstant.isPresent()) {
						TankConstant newConstant = editedConstant.get();
						log.debug("replace constant: {} with {}", constant, newConstant);
						return newConstant;
					}
					return constant;
				})
				.collect(Collectors.toList()));
		tankRoot.setUpdatedTime(DateTimeFormatter.ofPattern(TankRoot.TIME_FORMAT).format(OffsetDateTime.now()));
		tankRoot.setTankVersion(ConfigUtil.get("tank.version"));
		tankRoot.setTankGeneratorVersion(ConfigUtil.get("tank.generator.version"));
	}
	protected void saveTankFile(boolean overwrite) {
		if(tankFile == null) {
			JFileChooser fileChooser = new JFileChooser();
			FileNameExtensionFilter filter = new FileNameExtensionFilter("Tank Project File", "tank");
			fileChooser.setFileFilter(filter);
            int returnValue = fileChooser.showSaveDialog(this);
            if (returnValue == JFileChooser.APPROVE_OPTION) {
            	tankFile = fileChooser.getSelectedFile();
            	if(tankFile ==null) {
            		return;
            	}
            	String filePath = tankFile.getAbsolutePath();

                if (!filePath.endsWith(".tank")) {
                	tankFile = new File(filePath + ".tank");
                }
            }else {
				return;
			}
            if(tankFile.exists() && !overwrite && (!MessageUtil.isYes(TankGeneratorFrame.this, getString("message.confirm.root.file.already.exists")))) {
            		return;
            }
		}
		try(FileWriter fw = new FileWriter(tankFile)){
			new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(tankFile, tankRoot);
			tabbedPane.setAllEditorSaved(true);
			tankRootChanged = false;
			statusLabel.setText("Saved");
			Timer timer = new Timer(1500, e -> statusLabel.setText(" "));
			timer.setRepeats(false);
			timer.start();
		} catch (IOException e) {
			log.error("save tank file error",e);
			MessageUtil.showError(TankGeneratorFrame.this,"Error saving file: " + e.getMessage() );
		}
		
	}
	



	public String getString(String key) {
		return TextUtil.getInstance().getString(key);
	}

	public void changeStyle(String className) {
		generatorApp.changeStyle(className);
		
	}

	public void changeLanguage(Locale chinese) {
		generatorApp.changeLanguage(chinese);

	}

	public void openScriptDialog() {
		updateTankRoot();
		ObjectWriter objWriter = new ObjectMapper().writerWithDefaultPrettyPrinter();
		String json = null;
		try {
			json = objWriter.writeValueAsString(tankRoot);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		JDialog dialog = new JDialog(TankGeneratorFrame.this, "Script", true);
		dialog.getContentPane().add(new JScrollPane(new JTextArea(json,20,50)));
		dialog.pack();
		dialog.setVisible(true);
	}

	public void addConstant() {
		// ask constant name
		String newConstantName = MessageUtil.getInput(TankGeneratorFrame.this, getString("message.input.constant.name"));
		if(newConstantName == null) {
			return;
		}
		// check duplicate constant exists
		if(tankRoot.getConstants().stream()
				.anyMatch(constant ->constant.getName().equals(newConstantName))) {
			// constant already exists
			MessageUtil.showError(TankGeneratorFrame.this, getString("message.error.constant.already.exists") );
			return;
		}
		
		TankConstant newConstant = new TankConstant(newConstantName);
		tankRoot.getConstants().add(newConstant);
		tankRootChanged = true;
		refreshTankRoot();
		openConstantTab(newConstant, true);
	}

	public void deleteConstant(TankConstant constant) {
		// ask confirm delete
		if(MessageUtil.isYes(TankGeneratorFrame.this,getString("message.confirm.constant.delete"))) {
			tankRoot.getConstants().remove(constant);
			tankRootChanged = true;
			refreshTankRoot();
		}
	}

	public void openConstantTab(TankConstant constant, boolean newConstant) {
		tabbedPane.openConstantTab(constant, newConstant);
	}

	

}
